using System;
using System.Collections.Generic;

namespace Microsoft.WowAddonStudio.Lua.RuntimeService.WowBench
{
	/// <summary>
	/// Represents a history list for WoWBench commands.
	/// (custom representation of LinkedList<T>)
	/// </summary>
	public sealed class CommandHistory : ICommandHistory, IEventProvider
	{
		#region Public Events

		private event EventHandler<CommandHistoryEventArgs> commandAdded;

		/// <summary>
		/// Fires when a new command is added to <see cref="ICommandHistory"/>.
		/// </summary>
		public event EventHandler<CommandHistoryEventArgs> CommandAdded
		{
			add { commandAdded += value; }
			remove { commandAdded -= value; }
		}

		#endregion

		private readonly IWoWBenchCommandHandler commandHandler;
		private readonly List<ICommandEntry> commands = new List<ICommandEntry>();
		private readonly int capacity = -1;

		/// <summary>
		/// Initializes a new instance of the <see cref="CommandHistory"/> class.
		/// </summary>
		/// <param name="commandHandler"></param>
		public CommandHistory(IWoWBenchCommandHandler commandHandler)
			: this(commandHandler, -1)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CommandHistory"/> class.
		/// </summary>
		/// <param name="commandHandler"></param>
		/// <param name="capacity"></param>
		public CommandHistory(IWoWBenchCommandHandler commandHandler, int capacity)
		{
			this.commandHandler = commandHandler;
			this.capacity = ++capacity;
			AddDefaultCommand();
		}


		/// <summary>
		/// Indexer for <see cref="CommandHistory"/>.
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		internal ICommandEntry this[int index]
		{
			get { return commands[index]; }
			set { commands[index] = value; }
		}

		#region INavigable<ICommandEntry> Members

		/// <summary>
		/// Gets the current command entry.
		/// </summary>
		public ICommandEntry Current { get; private set; }

		/// <summary>
		/// Gets the current command entry.
		/// </summary>
		public ICommandEntry New { get; set; }

		/// <summary>
		/// Gets or sets the next command entry.
		/// </summary>
		public ICommandEntry Next
		{
			get
			{
				Current = Current.Next;
				while (!Current.Enabled)
					Current = Current.Next;
				return Current;
			}
			set { }
		}

		/// <summary>
		/// Gets or sets the previous command entry.
		/// </summary>
		public ICommandEntry Previous
		{
			get
			{
				Current = Current.Previous;
				while (!Current.Enabled)
					Current = Current.Previous;
				return Current;
			}
			set { }
		}

		/// <summary>
		/// Gets or sets the last command entry.
		/// </summary>
		public ICommandEntry Last
		{
			get
			{
				Current = commands[commands.Count - 1];
				while (!Current.Enabled)
					Current = Current.Previous;
				return Current;
			}
			set { }
		}

		/// <summary>
		/// Gets or sets the first command entry.
		/// </summary>
		public ICommandEntry First
		{
			get
			{
				Current = commands[0];
				while (!Current.Enabled)
					Current = Current.Next;
				return Current;
			}
			set { }
		}

		/// <summary>
		/// Removes all elements from the <see cref="CommandHistory"/>.
		/// </summary>
		public void Clear()
		{
			commands.Clear();
			AddDefaultCommand();
		}

		/// <summary>
		/// Reset the current chain of commands.
		/// </summary>
		public void Reset()
		{
			Current = commands[0];
		}

		#endregion

		/// <summary>
		/// Gets the number of elements actually contained in the <see cref="CommandHistory"/>.
		/// </summary>
		public int Count
		{
			get { return commands.Count; }
		}

		/// <summary>
		/// Gets the total number of elements the internal data structure can hold.
		/// </summary>
		public int Capacity
		{
			get { return capacity; }
		}

		/// <summary>
		/// Adds an object to the end of the CommandHistory.
		/// </summary>
		/// <param name="command">The object to be added to the end of the <see cref="CommandHistory"/>.
		/// </param>
		public void Add(ICommandEntry command)
		{
			if (command == null)
				throw new ArgumentNullException("command");

			if (Capacity >= -1)
			{
				if (Count >= Capacity && Capacity > 1)
				{
					SetCommandRelations(commands[2], commands[1].Previous, commands[2].Next);
					commands[1].Previous = null;
					commands[1].Next = null;
					commands.RemoveAt(1);
				}
				SetCommandRelations(command, commands[commands.Count - 1], commands[0]);
				Current = command;
				commands.Add(command);
				if (commandAdded != null)
					commandAdded(this, new CommandHistoryEventArgs(command.GetCommand()));
			}
		}

		/// <summary>
		/// Removes the first occurrence of a specific object from the <see cref="CommandHistory"/>.
		/// </summary>
		/// <param name="command">The object to remove from the <see cref="CommandHistory"/>.</param>
		public void Remove(ICommandEntry command)
		{
			if (command is NullCommand) return;

			SetCommandRelations(command.Next, command.Previous, command.Next.Next);

			command.Previous = null;
			command.Next = null;
			commands.Remove(command);
		}

		/// <summary>
		/// Add the default placeholder <see cref="NullCommand"/> to <see cref="CommandHistory"/>.
		/// </summary>
		private void AddDefaultCommand()
		{
			var nullCommand = new NullCommand(commandHandler);

			SetCommandRelations(nullCommand, nullCommand, nullCommand);
			Current = nullCommand;
			commands.Add(nullCommand);
		}

		/// <summary>
		/// Set relation between <see cref="ICommandEntry"/> objects.
		/// </summary>
		/// <param name="command">The <see cref="ICommandEntry"/> to be set.</param>
		/// <param name="prevCommand">The previous <see cref="ICommandEntry"/>.</param>
		/// <param name="nextCommand">The next <see cref="ICommandEntry"/>.</param>
		private static void SetCommandRelations(ICommandEntry command,
		                                        ICommandEntry prevCommand, ICommandEntry nextCommand)
		{
			command.Next = nextCommand;
			command.Previous = prevCommand;
			prevCommand.Next = command;
			nextCommand.Previous = command;
		}

		/// <summary>
		/// Detach all listeners from events.
		/// </summary>
		public void UnsubscribeEvents()
		{
			commandAdded = null;
		}


		/// <summary>
		/// Performs application-defined tasks associated with freeing,
		/// releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			UnsubscribeEvents();
		}

		#region NullCommand Entry

		/// <summary>
		/// Represents a NullCommand.
		/// </summary>
		private sealed class NullCommand : WoWBenchCommand
		{
			/// <summary>
			/// Initializes a new instance of the <see cref="NullCommand"/> class.
			/// </summary>
			/// <param name="commandHandler"></param>
			public NullCommand(IWoWBenchCommandHandler commandHandler)
				: this(commandHandler, String.Empty)
			{
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="NullCommand"/> class.
			/// </summary>
			/// <param name="parent"></param>
			/// <param name="command"></param>
			public NullCommand(IWoWBenchCommandHandler parent, string command)
				: base(parent, command)
			{
			}
		}

		#endregion


	}
}